package com.t3.tuio
{
	import com.t3.client.T3Client;
	import com.t3.osc.OSCConnection;
	import com.t3.osc.OSCConnectionEvent;
	import com.t3.osc.OSCMessage;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.utils.Dictionary;
	import flash.utils.Timer;

	public class TUIOClient extends EventDispatcher
	{	
		private var _OSCReceiver:OSCConnection;
		private var _port:uint = 3333;
		private var _address:String = "localhost";
		
		private var _currentFrame:int = 0;
		private var _lastFrame:int =0;
		
		private var _objectList:Dictionary;
		private var _newObjectList:Dictionary = new Dictionary();
		private var _aliveObjectList:Dictionary = new Dictionary();
		
		private var _cursorList:Dictionary;
		private var _newCursorList:Dictionary = new Dictionary();
		private var _aliveCursorList:Dictionary = new Dictionary();
		
		private var _updated:Boolean = false;
		
		public function TUIOClient( listener:ITUIOListener = null, address:String="localhost", port:uint=3000 ):void
		{
			this._objectList = T3Client.liveObjects;
			this._cursorList = T3Client.cursors;
			
			_port = port;
			_address = address;
			
			addTUIOListener( listener );
			connect();
			
			var timer:Timer = new Timer( 2000, 0 );
			timer.addEventListener( TimerEvent.TIMER, keepAlive );
			timer.start();
		}
		
		private function keepAlive( event:TimerEvent ):void
		{
			if( !_updated )
			{
				disconnect();
				connect();
				trace( "reconnect()" );
			}
			
			_updated = false;
		}
		
		public function connect():void
		{
			_OSCReceiver = new OSCConnection(_address, _port);
			_OSCReceiver.addEventListener(OSCConnectionEvent.OSC_PACKET_IN, onPacketIn);
			
			_OSCReceiver.addEventListener(Event.CONNECT,onConnect);
			_OSCReceiver.addEventListener(Event.CLOSE,onClose);
			_OSCReceiver.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			_OSCReceiver.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
		}
		
		public function disconnect():void
		{
			if( _OSCReceiver != null )
			{
				_OSCReceiver.close();
				
				_OSCReceiver.removeEventListener(OSCConnectionEvent.OSC_PACKET_IN, onPacketIn);
				
				_OSCReceiver.removeEventListener(Event.CONNECT,onConnect);
				_OSCReceiver.removeEventListener(Event.CLOSE,onClose);
				_OSCReceiver.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
				_OSCReceiver.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			}
		}
		
		public function addTUIOListener( listener:ITUIOListener ):void
		{
			if( listener != null )
			{
				this.addEventListener(TUIOEvent.ADD_TUIO_OBJ, listener.addObject);
				this.addEventListener(TUIOEvent.ADD_TUIO_CUR, listener.addCursor);
				this.addEventListener(TUIOEvent.REMOVE_TUIO_CUR, listener.removeCursor);
				this.addEventListener(TUIOEvent.REMOVE_TUIO_OBJ, listener.removeObject );
				this.addEventListener(TUIOEvent.UPDATE_TUIO_CUR, listener.updateCursor );
				this.addEventListener(TUIOEvent.UPDATE_TUIO_OBJ, listener.updateObject );
				this.addEventListener(TUIOEvent.REFRESH, listener.refresh);
			}
		}
		
		private function onPacketIn(event:OSCConnectionEvent):void
		{
			for each( var message:OSCMessage in event.data.messages )
			{
				processMessage( message );
			}
			
			_updated = true;
		}
		
		private function processMessage(message:OSCMessage):void
		{
			switch( message.name )
			{				
				case "/tuio/2Dobj": processObject( message ); break;					
				case "/tuio/2Dcur": processCursor( message ); break;
			}
		}
		
		private function processObject( message:OSCMessage ):void
		{
			var command:String = (String) (message.getArgumentValue( 0 ));
			
			if( (command == "set") && (_currentFrame >= _lastFrame) )
			{
				var data:Object = new Object();
				data.s_id = (int)(message.getArgumentValue(1));
				data.f_id = (int)(message.getArgumentValue(2));
				data.x = (Number)(message.getArgumentValue(3));
				data.y = (Number)(message.getArgumentValue(4));
				data.a = (Number)(message.getArgumentValue(5));
				data.dx = (Number)(message.getArgumentValue(6));
				data.dy = (Number)(message.getArgumentValue(7));
				data.da = (Number)(message.getArgumentValue(8));
				data.m = (Number)(message.getArgumentValue(9));
				data.r = (Number)(message.getArgumentValue(10));
				
				// new object
				if( _objectList[ data.s_id ] == null )
				{	
					dispatchEvent( new TUIOEvent( TUIOEvent.ADD_TUIO_OBJ,data ) );

				}
				// update object
				else
				{
					dispatchEvent( new TUIOEvent( TUIOEvent.UPDATE_TUIO_OBJ, data ) );
				}
			}
			else if( ( command=="alive" ) && ( _currentFrame >= _lastFrame ) )
			{
				var s_id:uint;
				
				for( var i:uint = 1; i < message.arguments.length; i++ )
				{
					s_id = (uint) ( message.getArgumentValue( i ) );
					_newObjectList[ s_id ] = s_id;
					
					if( _aliveObjectList[ s_id ] != null)
					{
						_aliveObjectList[ s_id ] = null;
						delete( _aliveObjectList[ s_id ] );
					}
				}
				
				for each( s_id in _aliveObjectList ) 
				{
					data = new Object();
					data.s_id = s_id;
					data.f_id = ( ( TUIOObject ) ( _objectList[ s_id ] ) ).f_id;
					
					dispatchEvent( new TUIOEvent( TUIOEvent.REMOVE_TUIO_OBJ, data ) );
				}
				
				var buffer:Dictionary = _aliveObjectList;
				_aliveObjectList = _newObjectList;
				_newObjectList = buffer;
				
				for( var key:Object in _newObjectList )
				{
					_newObjectList[ key ] = null;
					delete( _newObjectList[ key ] );
				}
			}
			else if( command == "fseq" )
			{
				_lastFrame = _currentFrame;
				_currentFrame = (int) ( message.getArgumentValue( 1 ) );
				
				if( _currentFrame == -1 )
					_currentFrame = _lastFrame;
				
				if( _currentFrame >= _lastFrame )
					dispatchEvent( new TUIOEvent( TUIOEvent.REFRESH ) );	
			}
		}
		
		private function processCursor( message:OSCMessage ):void
		{
			var command:String = (String) ( message.getArgumentValue( 0 ) );
			
			if( ( command == "set" ) && ( _currentFrame >= _lastFrame ) )
			{	
				var data:Object = new Object();
				
				data.s_id = (int)(message.getArgumentValue(1));
				data.x = (Number)(message.getArgumentValue(2));
				data.y = (Number)(message.getArgumentValue(3));
				data.dx = (Number)(message.getArgumentValue(4));
				data.dy = (Number)(message.getArgumentValue(5));
				data.m = (Number)(message.getArgumentValue(6));

				// new cursor
				if( _cursorList[ data.s_id ] == null )
				{
					dispatchEvent( new TUIOEvent( TUIOEvent.ADD_TUIO_CUR,data ) );
				}
				// update cursor
				else
				{
					dispatchEvent( new TUIOEvent( TUIOEvent.UPDATE_TUIO_CUR,data ) );
				}
			}
			else if( (command=="alive")&&(_currentFrame >= _lastFrame) )
			{
				var s_id:int;
				
				for(var i:uint = 1; i < message.arguments.length; i++)
				{
					s_id = (int)(message.getArgumentValue(i));
					_newCursorList[ s_id ] = s_id;
					
					if( _aliveCursorList[ s_id ] != null)
					{
						_aliveCursorList[ s_id ] = null;
						delete( _aliveCursorList[ s_id ] );
					}
				}
				
				for each( s_id in _aliveCursorList )
				{
					data = new Object();
					data.s_id = s_id;
					
					dispatchEvent( new TUIOEvent( TUIOEvent.REMOVE_TUIO_CUR, data ) );
				}
				
				var buffer:Dictionary = _aliveCursorList;
				_aliveCursorList = _newCursorList;
				_newCursorList = buffer;
				
				for( var key:Object in _newCursorList )
				{
					_newCursorList[ key ] = null;
					delete( _newCursorList[ key ] );
				}
			}
			else if( command == "fseq" )
			{
				_lastFrame = _currentFrame;
				_currentFrame = (int) ( message.getArgumentValue( 1 ) );
				
				if( _currentFrame == -1 )
					_currentFrame = _lastFrame;
				
				if( _currentFrame >= _lastFrame )
					dispatchEvent( new TUIOEvent( TUIOEvent.REFRESH ) );	
			}
		}
		
		// *** event handler to respond to successful connection attempt
		protected function onConnect( event:Event ) : void
		{
			dispatchEvent(event);
		}
	
	
		// *** event handler called when server kills the connection
		protected function onClose ( event:Event ) : void
		{
			dispatchEvent(event);
		}
	
		
		protected function onIOError( event:IOErrorEvent ):void
		{
			dispatchEvent(event);
		}
		
		protected function onSecurityError( event:Event ):void
		{
			dispatchEvent(event);
		}
	}
}